/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.menu;

import org.apache.commons.collections.FastHashMap;
import org.apache.commons.digester.Digester;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletContext;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;


/**
 * <p>Provides functionality to load menu components from config</p>
 * <p>This class was copied from Struts Menu and slightly modified</p>
 * <p><a href="MenuBase.java.html"><i>View Source</i></a></p>
 *
 * @author Scott Sayles
 * @author Matt Raible
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 */
public class MenuRepository implements LoadableResource {
	//~ Static fields/initializers =============================================

	public static final String MENU_REPOSITORY_KEY =
	        "com.blandware.atleap.webapp.menu.MENU_REPOSITORY";
	protected transient Log log = LogFactory.getLog(MenuRepository.class);

	//~ Instance fields ========================================================

	protected String config = null;
	protected String name = null;
	protected ServletContext servletContext = null;
	protected FastHashMap menus = new FastHashMap();
	protected FastHashMap templates = new FastHashMap();

	//~ Methods ================================================================

	/**
	 * Returns set of top-level menu names
	 *
	 * @return Set of top-level menu names
	 */
	public Set getMenuNames() {
		return menus.keySet();
	}

	/**
	 * Convenience method for dynamic menus - returns the top-level menus only.
	 *
	 * @return Top-level menus
	 */
	public List getTopMenus() {
		List topMenus = new ArrayList();
		if ( menus == null ) {
			log.warn("No menus found in repository!");
			return topMenus;
		}

		for ( Iterator it = menus.keySet().iterator(); it.hasNext(); ) {
			String name = (String) it.next();
			MenuComponent menu = getMenu(name);
			if ( menu.getParent() == null ) {
				topMenus.add(menu);
			}
		}
		return topMenus;
	}

	/**
	 * Returns top-level menu by its name
	 *
	 * @param menuName Name of menu to return
	 * @return Top-level menu by name
	 */
	public MenuComponent getMenu(String menuName) {
		return (MenuComponent) menus.get(menuName);
	}

	/**
	 * Initializes digester to get menus from config
	 *
	 * @return Initialized digester instance
	 */
	protected Digester initDigester() {
		Digester digester = new Digester();
		digester.setClassLoader(Thread.currentThread().getContextClassLoader());
		digester.push(this);

		// 1
		digester.addObjectCreate("MenuConfig/Menus/Menu",
		        "com.blandware.atleap.webapp.menu.MenuComponent", "type");
		digester.addSetProperties("MenuConfig/Menus/Menu");
		digester.addSetNext("MenuConfig/Menus/Menu", "addMenu");

		// 2
		digester.addObjectCreate("MenuConfig/Menus/Menu/Item",
		        "com.blandware.atleap.webapp.menu.MenuComponent", "type");
		digester.addSetProperties("MenuConfig/Menus/Menu/Item");
		digester.addSetNext("MenuConfig/Menus/Menu/Item", "addMenuComponent",
		        "com.blandware.atleap.webapp.menu.MenuComponent");

		// 3
		digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item",
		        "com.blandware.atleap.webapp.menu.MenuComponent", "type");
		digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item");
		digester.addSetNext("MenuConfig/Menus/Menu/Item/Item",
		        "addMenuComponent", "com.blandware.atleap.webapp.menu.MenuComponent");

		// 4
		digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item",
		        "com.blandware.atleap.webapp.menu.MenuComponent", "type");
		digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item");
		digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item",
		        "addMenuComponent", "com.blandware.atleap.webapp.menu.MenuComponent");

		// 5
		digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item",
		        "com.blandware.atleap.webapp.menu.MenuComponent", "type");
		digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");
		digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item",
		        "addMenuComponent", "com.blandware.atleap.webapp.menu.MenuComponent");

		// 6
		digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item",
		        "com.blandware.atleap.webapp.menu.MenuComponent", "type");
		digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item");
		digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item",
		        "addMenuComponent", "com.blandware.atleap.webapp.menu.MenuComponent");

		// 7
		digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item/Item",
		        "com.blandware.atleap.webapp.menu.MenuComponent", "type");
		digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item/Item");
		digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item/Item",
		        "addMenuComponent", "com.blandware.atleap.webapp.menu.MenuComponent");

		return digester;
	}

	/**
	 * Adds a new menu.  This is called when parsing the menu xml definition.
	 *
	 * @param menu The menu component to add.
	 */
	public void addMenu(MenuComponent menu) {
		if ( menus.containsKey(menu.getName()) ) {
			if ( log.isDebugEnabled() ) {
				log.warn("Menu '" + menu.getName()
				        + "' already exists in repository");
			}
			List children = getMenu(menu.getName()).getComponents();
			if ( children != null && menu.getComponents() != null ) {
				for ( Iterator it = children.iterator(); it.hasNext(); ) {
					MenuComponent child = (MenuComponent) it.next();
					menu.addMenuComponent(child);
				}
			}
		}
		menus.put(menu.getName(), menu);
	}

	/**
	 * Allows easy removal of a menu by its name.
	 *
	 * @param name Name of menu to remove
	 */
	public void removeMenu(String name) {
		if ( menus.containsKey(name) ) {
			menus.remove(getMenu(name));
		}
	}

    /**
     * Loads the menu repository
     *
     * @throws LoadableResourceException
     */
	public void load() throws LoadableResourceException {
		if ( getServletContext() == null ) {
			throw new LoadableResourceException("no reference to servlet context found");
		}

		InputStream input = null;
		Digester digester = initDigester();

		try {
			input = getServletContext().getResourceAsStream(config);
			digester.parse(input);

			menus.setFast(true);
		} catch ( Exception e ) {
			e.printStackTrace();
			throw new LoadableResourceException("Error parsing resource file: " +
			        config + " nested exception is: " + e.getMessage());
		} finally {
			try {
				input.close();
			} catch ( Exception e ) {
			}
		}
	}

    /**
     * Reloads the menu repository
     *
     * @throws LoadableResourceException
     */
	public void reload() throws LoadableResourceException {
		menus.setFast(false);
		menus.clear();
		load();
	}

    /**
     * Sets load parameter
     *
     * @param loadParam the parameter to set
     */
	public void setLoadParam(String loadParam) {
		config = loadParam;
	}

    /**
     * Returns load parameter
     *
     * @return load parameter
     */
	public String getLoadParam() {
		return config;
	}

    /**
     * Sets name
     *
     * @param name name to set
     */
	public void setName(String name) {
		this.name = name;
	}

    /**
     * Returns name
     *
     * @return name
     */
	public String getName() {
		return name;
	}

    /**
     * Returns servlet context
     *
     * @return servlet context
     */
	public ServletContext getServletContext() {
		return servletContext;
	}

    /**
     * Sets servlet context
     *
     * @param context servlet context to set
     */
	public void setServletContext(ServletContext context) {
		this.servletContext = context;
	}
}
